package graph;

import static ogl.vecmathimp.FactoryDefault.vecmath;
import static org.lwjgl.opengl.GL11.glDrawArrays;
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;

import java.nio.FloatBuffer;

import ogl.app.Input;
import ogl.vecmath.Color;
import ogl.vecmath.Matrix;
import ogl.vecmath.Vector;
import ogl.vecmathimp.MatrixImp;

import org.lwjgl.BufferUtils;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.GL11;

import shader.Shader;

public class Wuerfel extends Shape {
	private float el;
	private FloatBuffer positionData;
	private FloatBuffer colorData;
	private Vector mittelpunkt;
	
	// The attribute indices for the vertex data.
	public static int vertexAttribIdx = 0;
	public static int colorAttribIdx = 1;
	
	// Initialize the rotation angle of the cube
	private float angle = 0;
	
	// Shader
	private Shader s;
	
	// Auxiliary class to represent a single vertex.
	private Color[] c = new Color[]{ 
		      col(1, 0, 0), 
		      col(1, 0, 0), 
		      col(1, 0, 0), 
		      col(1, 0, 0),
		      col(1, 1, 0), 
		      col(1, 1, 0), 
		      col(1, 1, 0), 
		      col(1, 1, 0) 
	};
	
	// The positions of the cube vertices.
	private Vector[] p;
	
	// Vertices combine position and color information. Every four vertices define
	// one side of the cube.
	private Vertex[] vertices;
	
	// Rotation Vector
	private Vector rotVector = vecmath.vector(0, 0, 1);
	
	// Translation Vector 
	private Vector transVector = vecmath.vector(0, 0, 0);
	
	public Wuerfel() {
		s = Scene.getDefaultShader();
		this.mittelpunkt = vec(0,0,0);
		el = 0.5f;
		
		initData();
	}
	
	public Wuerfel(Shader s) {
		this.mittelpunkt = vec(0,0,0);
		el = 0.5f;
		
		this.s = s;
		initData();
	}
	
	/**
	 * Konstruktor für einen Würfel um den Nullpunkt
	 * @param mittelpunkt Mittelpunkt des Würfels
	 * @param edgeLength Länge einer Würfelkante
	 */
	public Wuerfel(Shader s, float edgeLength) {
		el = edgeLength;
		this.mittelpunkt = vec(0,0,0);
		
		this.s = s;
		initData();
	}
	
	/**
	 * Konstruktor für einen Würfel um den übergebenen Mittelpunkt
	 * @param mittelpunkt Mittelpunkt des Würfels
	 * @param edgeLength Länge einer Würfelkante
	 */
	public Wuerfel(Shader s, Vector mittelpunkt, float edgeLength) {
		el = edgeLength;
		this.mittelpunkt = mittelpunkt;

		this.s = s;
		initData();
	}
	
	/**
	 * Konstruktor für einen Würfel, bei dem die Farbe für jede Würfelseite
	 * einzeln übergeben werden kann
	 * @param mittelpunkt Mittelpunkt des Würfels
	 * @param edgeLength Länge einer Würfelkante
	 * @param c Array mit den Farben für die Seiten des Würfels (muss Länge 8 haben)
	 */
	public Wuerfel(Shader s, Vector mittelpunkt, float edgeLength, Color[] c) {
		el = edgeLength;
		this.mittelpunkt = mittelpunkt;
		if (c.length == 8) {
			this.c = c;
		}
		
		this.s = s;;
		initData();
	}
	
	/**
	 * Konstruktor für einen Würfel, bei dem alle Seiten die gleiche
	 * Farbe haben.
	 * @param mittelpunkt Mittelpunkt des Würfels
	 * @param edgeLength Länge einer Würfelkante
	 * @param c Color für alle Würfelseiten
	 */
	public Wuerfel(Shader s, Vector mittelpunkt, float edgeLength, Color c) {
		el = edgeLength / 2;
		this.mittelpunkt = mittelpunkt;
		this.c = new Color[8];
		for (int i = 0; i < 8; i++) {
			this.c[i] = c;
		}
		
		this.s = s;
		initData();
	}
	
	//
	//     6 ------- 7 
	//   / |       / | 
	//  3 ------- 2  | 
	//  |  |      |  | 
	//  |  5 -----|- 4 
	//  | /       | / 
	//  0 ------- 1
	//
	// Im Uhrzeigersinn: Zeigt zu mir
	// Gegen Uhrzeigersinn: Zeigt weg von mir
	
	private void initData() {
		Float xVer = mittelpunkt.x();
		Float yVer = mittelpunkt.y();
		Float zVer = mittelpunkt.z();
		
		p = new Vector[]{ 
				vec(-el + xVer, -el + yVer, el + zVer), 
				vec(el + xVer, -el + yVer, el + zVer),
				vec(el + xVer, el + yVer, el + zVer), 
				vec(-el + xVer, el + yVer, el + zVer), 
				vec(el + xVer, -el + yVer, -el + zVer), 
				vec(-el + xVer, -el + yVer, -el + zVer),
				vec(-el + xVer, el + yVer, -el + zVer), 
				vec(el + xVer, el + yVer, -el + zVer) 
		};
		
		vertices = new Vertex[] {
			    // front
			    v(p[0], c[0]), v(p[1], c[1]), v(p[2], c[2]), v(p[3], c[3]),
			    // back
			    v(p[4], c[4]), v(p[5], c[5]), v(p[6], c[6]), v(p[7], c[7]),
			    // right
			    v(p[1], c[1]), v(p[4], c[4]), v(p[7], c[7]), v(p[2], c[2]),
			    // top
			    v(p[3], c[3]), v(p[2], c[2]), v(p[7], c[7]), v(p[6], c[6]),
			    // left
			    v(p[5], c[5]), v(p[0], c[0]), v(p[3], c[3]), v(p[6], c[6]),
			    // bottom
			    v(p[5], c[5]), v(p[4], c[4]), v(p[1], c[1]), v(p[0], c[0]) 
		};
		
		// Prepare the vertex data arrays.
	    // Compile vertex data into a Java Buffer data structures that can be
	    // passed to the OpenGL API efficiently.
	    positionData = BufferUtils.createFloatBuffer(vertices.length
	        * vecmath.vectorSize());
	    colorData = BufferUtils.createFloatBuffer(vertices.length
	        * vecmath.colorSize());

	    for (Vertex v : vertices) {
	      positionData.put(v.position.asArray());
	      colorData.put(v.color.asArray());
	    }
	    positionData.rewind();
	    colorData.rewind();	    
	}
	
	@Override
	public MatrixImp draw() {		
		// The modeling transformation. Object to world
		Matrix modelMatrix;
		modelMatrix = vecmath.translationMatrix(transVector);
		modelMatrix = modelMatrix.mult(vecmath.rotationMatrix(rotVector, angle));
		modelMatrix = modelMatrix.mult(vecmath.translationMatrix(vecmath.vector(1, 0, 0)));
		
		// The inverse camera transformation. World space to camera space.
		Matrix viewMatrix = vecmath.lookatMatrix(vecmath.vector(0f, 0f, 3f),
				vecmath.vector(0f, 0f, 0f), vecmath.vector(0f, 1f, 0f));
				
		s.setModelMatrixUniform(modelMatrix);
		s.setViewMatrixUniform(viewMatrix);
		
		// Enable the vertex data arrays (with indices 0 and 1). We use a vertex
		// position and a vertex color.
		glVertexAttribPointer(vertexAttribIdx, 3, false, 0, positionData);
		glEnableVertexAttribArray(vertexAttribIdx);
		glVertexAttribPointer(colorAttribIdx, 3, false, 0, colorData);
		glEnableVertexAttribArray(colorAttribIdx);
		
		// Draw the triangles that form the cube from the vertex data arrays.
		glDrawArrays(GL11.GL_QUADS, 0, vertices.length);
		
		return null;
	}
	
	public void simulate(float elapsed, Input input) {
		// Pressing key 'r' toggles the cube animation.
		if (input.isKeyToggled(Keyboard.KEY_R))
			// Increase the angle with a speed of 90 degrees per second.
			angle += 90 * elapsed;
	}

	/**
	 * Setzt die Farben der Würfelseiten auf die übergebenen.
	 * @param c
	 */
	public void setColor(Color[] c) {
		if (c.length == 8) {
			this.c = c;
		}
		initData();
	}
	
	/**
	 * Setzt die Farbe aller Würfelseiten auf die übergebene.
	 * @param c
	 */
	public void setColor(Color c) {
		for (int i = 0; i < 8; i++) {
			this.c[i] = c;
		}
		initData();
	}
	
	public void setRotationVector(Vector v) {
		rotVector = v;
	}
	
	public void setTranslationVector(Vector v) {
		transVector = v;
	}
	
	private class Vertex {
	    Vector position;
	    Color color;

	    Vertex(Vector p, Color c) {
	    	position = p;
	    	color = c;
	    }
	}

	// Make construction of vertices easy on the eyes.
	private Vertex v(Vector p, Color c) {
	   return new Vertex(p, c);
	}

	// Make construction of vectors easy on the eyes.
	private Vector vec(float x, float y, float z) {
	   return vecmath.vector(x, y, z);
	}

	// Make construction of colors easy on the eyes.
	private Color col(float r, float g, float b) {
		return vecmath.color(r, g, b);
	}	
	
}

